	package com.ldh.mod4j.businessdomain.generator.hibernate

import com.google.inject.Inject
import org.mod4j.dslcommon.generator.helpers.NameMapper
import org.mod4j.businessdomain.generator.helpers.BusinessClassHelpers
import com.ldh.mod4j.businessdomain.businessDomainDsl.BusinessClass
import org.mod4j.dslcommon.xpand.java.JavaComments
import com.ldh.mod4j.businessdomain.businessDomainDsl.BusinessDomainModel
import org.mod4j.dslcommon.generator.helpers.ModelHelpers
import org.mod4j.dslcommon.xpand.xml.XmlComments
import org.mod4j.dslcommon.generator.helpers.ProjectProperties
import com.ldh.mod4j.businessdomain.businessDomainDsl.Property
import com.ldh.mod4j.businessdomain.businessDomainDsl.Association
import org.mod4j.businessdomain.generator.helpers.BusinessRuleHelpers
import com.ldh.mod4j.businessdomain.businessDomainDsl.StringProperty
import com.ldh.mod4j.businessdomain.businessDomainDsl.EnumerationProperty
import org.mod4j.businessdomain.generator.helpers.BusinessDomainModelHelpers
import com.ldh.mod4j.businessdomain.businessDomainDsl.DateTimeProperty
import com.ldh.mod4j.businessdomain.businessDomainDsl.DecimalProperty

class HibernateMapping {
	@Inject extension XmlComments xmlComments
	
	def header(BusinessClass clazz)'''
<?xml version="1.0"?>

<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

«xmlComments.regenerationWarningHeader ("Mod4j BusinessDomainDsl generator: HibernateMapping.xtend", clazz)»

<!--     
     Mapping for class : «clazz.name»
     Description       : «clazz.description»
-->
	'''
	
	def mapping(BusinessClass clazz)'''
«header(clazz)»

<hibernate-mapping
    default-access="field"
    package="«NameMapper::getBusinessModelDomainPackage(BusinessDomainModelHelpers::getModel(clazz).name)»">
        
        «IF clazz.superclass == null»
            «rootClassMapping(clazz)»
        «ELSEIF ProjectProperties::getHibernate_InheritanceMappingStrategy() == "table.per.concrete.class"»
            «concreteClassMapping(clazz)»
        «ELSEIF ProjectProperties::getHibernate_InheritanceMappingStrategy() == "table.per.subclass"»
            «subclassMapping(clazz)»
        «ENDIF»
</hibernate-mapping>
'''
	def rootClassMapping(BusinessClass clazz)'''
    <class name="«NameMapper::javaDomainClass(clazz.name)»"
           optimistic-lock="version">
        <cache usage="read-write" />
        <id name="id" type="java.lang.Long">
            <generator class="«ProjectProperties::getHibernate_mapping_class_id_generator_class()»" />
        </id>
        <version name="version" unsaved-value="negative" />
        «featureMappings(clazz)»
    </class>
	'''

	def concreteClassMapping(BusinessClass clazz)'''
    <union-subclass
        name="«NameMapper::javaDomainClass(clazz.name)»"
        extends="«NameMapper::javaDomainClass(clazz.superclass.name)»">
        «featureMappings(clazz)»
    </union-subclass>
	'''

	def subclassMapping(BusinessClass clazz)'''
    <joined-subclass
        name="«NameMapper::javaDomainClass(clazz.name)»"
        extends="«NameMapper::javaDomainClass(clazz.superclass.name)»">
        <key column="ID"/>
        «featureMappings(clazz)»
    </joined-subclass>
	'''

	def String featureMappings(BusinessClass clazz){
		var result = ""
		for (Property  p : BusinessClassHelpers::getNotDerviedProperties(clazz)){
			result = result + mapping(p)
		}
		for (Association  a : BusinessClassHelpers::getAssociationsTo(clazz)){
			result = result + mappingsTo(a)
		}
		for (Association a: BusinessClassHelpers::getBidirectionalAssociationsFrom(clazz)){
			result = result + mappingsFrom(a)
		}
		return result
	}

	def mapping(Property property)'''
		«val uniqueRule = BusinessRuleHelpers::getUniqueRule(property)»
		<property name="«property.name»"«IF uniqueRule != null» unique-key="«uniqueRule.name»"«ENDIF»«IF !property.nullable» not-null="true"«ENDIF»/>
	'''

	def mapping (StringProperty property)'''
        «val uniqueRule = BusinessRuleHelpers::getUniqueRule(property)»
        <property name="«property.name»"«IF property.maxLength > -1» length="«property.maxLength»"«ENDIF»«IF uniqueRule != null» unique-key="«uniqueRule.name»"«ENDIF»«IF !property.nullable» not-null="true"«ENDIF»/>
	'''

	def mapping(EnumerationProperty property)'''
		«val uniqueRule = BusinessRuleHelpers::getUniqueRule(property)»
		<property name="«property.name»"«IF uniqueRule != null » unique-key="«uniqueRule.name»"«ENDIF»«IF !property.nullable» not-null="true"«ENDIF»>
		    <type name="«NameMapper::getFqnGenericEnumUserType()»">
		        <param name="enumClass">«NameMapper::getFqnDomainClass(BusinessDomainModelHelpers::getModel(property).name, property.name)»</param>
		    </type>
		</property>
	'''

	def mapping(DateTimeProperty property)'''
		«val uniqueRule = BusinessRuleHelpers::getUniqueRule(property)»
		<property name="«property.name»"«IF uniqueRule != null» unique-key="«uniqueRule.name»"«ENDIF»«IF !property.nullable» not-null="true"«ENDIF» type="org.joda.time.contrib.hibernate.PersistentDateTime" />
	'''

	def mapping(DecimalProperty property)'''
		«val uniqueRule = BusinessRuleHelpers::getUniqueRule(property)»
		<property name="«property.name»"«IF property.precision > 0» precision="«property.precision»"«ENDIF»«IF property.scale > -1» scale="«property.scale»"«ENDIF»		 
			«IF uniqueRule != null»
				unique-key="«uniqueRule.name»"
			«ENDIF»
			«IF !property.nullable»
			    not-null="true"
			«ENDIF»
		/>
	'''

 	//mapping for outgoing associations
	def String mappingsTo(Association association){
		var result = ""
		if(BusinessClassHelpers::isOne2Many(association)){
			if(association.ordered){
				result = result + orderedOne2manyMappingTo(association)
			}else{
				result = result + one2manyMappingTo(association)
			}
		}else if(BusinessClassHelpers::isOne2One(association)){
			result = result + one2oneMappingTo(association)
		}else if(BusinessClassHelpers::isMany2One(association)){
			result = result + many2oneMappingTo(association)
		}else if(BusinessClassHelpers::isMany2Many(association)){
			if(association.ordered){
				result = result + orderedMany2manyMappingTo(association)
			}else{
				result = result + many2manyMappingTo(association)
			}
		}else{
			result = result + "<!-- WARNING mappingsTo(Association) no generation for : «association.eClass.name» -->"
		}
		return result
	}

	def String mappingsFrom(Association association){
		var result = ""
		if(association.bidirectional){
			if(BusinessClassHelpers::isOne2Many(association)){
				result = result + one2manyMappingFrom(association)
			}else if(BusinessClassHelpers::isOne2One(association)){
				result = result + one2oneMappingFrom(association)
			}else if(BusinessClassHelpers::isMany2One(association)){
				result = result + many2oneMappingFrom(association)
			}else if(BusinessClassHelpers::isMany2Many(association)){
				result = result + many2manyMappingFrom(association)
			}else{	
				result = result + "<!-- WARNING mappingsFrom(Association) no generation for : «association.eClass.name» -->"		
			}
		}
		return result
	}

	def one2manyMappingTo(Association association)'''
		<!-- one2manyMappingTo«IF association.bidirectional», bi-directional«ENDIF» -->
		<set name="«association.targetRoleName»"
		    cascade="save-update" «IF association.bidirectional»inverse="true"«ENDIF»>
		    <key column="«association.sourceRoleName»_ID"/>
		    <one-to-many class="«NameMapper::javaDomainClass(association.target.name)»" />
		</set>
	'''

	def orderedOne2manyMappingTo(Association association)'''
        <!-- orderedOne2manyMappingTo «IF association.bidirectional», bi-directional«ENDIF» -->
        <list name="«association.targetRoleName»"
            cascade="save-update" «IF association.bidirectional»inverse="true"«ENDIF»>
            <key column="«association.sourceRoleName»_ID"/>
            <list-index column="orderFor«association.sourceRoleName.toFirstUpper()»" />
            <one-to-many class="«NameMapper::javaDomainClass(association.target.name)»" />
        </list>
	'''

	/**
	 * Reverse navigable association from the target.
	 * This is generat3ed in the target class.
	 */
	def one2manyMappingFrom(Association association)'''
		<!-- one2manyMappingFrom, bi-directional -->
		<many-to-one name="«association.sourceRoleName»"
		    class="«NameMapper::javaDomainClass(association.source.name)»"
		    not-null="false" column="«association.sourceRoleName»_ID" />
	'''

	def many2oneMappingTo(Association association)'''
		<!-- many2oneMappingTo «IF association.bidirectional», bi-directional«ENDIF» -->
		<many-to-one name="«association.targetRoleName»"
		    cascade="save-update"
		    class="«NameMapper::javaDomainClass(association.target.name)»" 
		    not-null="false" column="«association.targetRoleName»_ID" />
	'''

	/**
	 * Reverse navigable association from the target.
	 * This is generat3ed in the target class.
	 */
	def many2oneMappingFrom(Association association)'''
        <!-- many2oneMappingFrom, bi-directional -->
        <set name="«association.sourceRoleName»" inverse="true"
            cascade="save-update">
            <key column="«association.targetRoleName»_ID"/>
            <one-to-many class="«NameMapper::javaDomainClass(association.source.name)»" />
        </set>
	'''

	def one2oneMappingTo(Association association)'''
		<!-- one2oneMappingTo «IF association.bidirectional», bi-directional«ENDIF» -->
		<many-to-one name="«association.targetRoleName»" 
			column="«association.targetRoleName»_ID"
			cascade="save-update"
			unique="true"
			class="«NameMapper::javaDomainClass(association.target.name)»" /> 
	'''

	def one2oneMappingFrom(Association association)'''
		<!-- one2oneMappingFrom, bi-directional -->
		<one-to-one name="«association.sourceRoleName»" 
			property-ref="«association.targetRoleName»"
			class="«NameMapper::javaDomainClass(association.source.name)»"
		 /> 
	'''

	def many2manyMappingTo(Association association)'''
        <!-- many2manyMappingTo «IF association.bidirectional», bi-directional«ENDIF» -->
        <set name="«association.targetRoleName»" table="«association.sourceRoleName»_«association.targetRoleName»">
            <key column="«association.sourceRoleName»_ID" not-null="true" />
            <many-to-many class="«NameMapper::javaDomainClass(association.target.name)»" column="«association.targetRoleName»_ID" />
        </set>
	'''

	def orderedMany2manyMappingTo(Association association)'''
        <!-- orderedMany2manyMappingTo «IF association.bidirectional», bi-directional«ENDIF» -->
        <list name="«association.targetRoleName»" table="«association.sourceRoleName»_«association.targetRoleName»">
            <key column="«association.sourceRoleName»_ID" not-null="true" />
            <list-index column="orderFor«association.sourceRoleName.toFirstUpper()»" />
            <many-to-many class="«NameMapper::javaDomainClass(association.target.name)»" column="«association.targetRoleName»_ID" />
        </list>
	'''


 	def many2manyMappingFrom(Association association)'''
        <!-- many2manyMappingFrom, bi-directional -->
        <set name="«association.sourceRoleName»" inverse="true" table="«association.sourceRoleName»_«association.targetRoleName»">
            <key column="«association.targetRoleName»_ID" not-null="true" />
            <many-to-many class="«NameMapper::javaDomainClass(association.source.name)»" column="«association.sourceRoleName»_ID" />
        </set>
	'''

}